/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package  cgl.narada.protocol;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import cgl.narada.util.ByteUtilities;

/** Gateway.java
 * The individual element in the connectivity graph which we construct to -
 * <li> Compute the best hop that should be taken from the <i>vertex Node</i>
 *      to reach any unit within the system.
 * <li> To detect partitions - This could either be a system partition or
 *      a unit partition  within a super unit. The knowledge of such partitions
 *      enable us to come up with alternate routing schemes or induce 
 *      <i>roam</i> within clients connected to the partitioned sub-unit or
 *      sub system. The feature tells a node about partitions that concern the
 *      node's routing abilities.
 * @author Shrideep Pallickara 
 * $Revision$
 * $Date$
 */ 

public class Gateway implements ProtocolDebugFlags {
  LinkCostMatrix linkCost;
  
  /** Lower level to higher level gateway */
  private Vector downLink; 
  
  /** Describes connection at the same level, and to a lower level gateway */
  private Vector lateralAndUpLink;
  
  /** Specifies the level of the gateway */
  private short level;
  
  /** Indicates the cost to reach the hosting node */
  private short cost;
  
  /** The node identifier */
  private NodeAddress node;
  
  /** Shortest Path to the vertex */
  private Path vertexPath;
  
  /** The VertexNode */
  private Gateway vertexNode;

  /** boolean for vertex */
  private boolean isVertex = false;

  /** This constructor is used the first time a node is assigned a 
      logical address. 
      @param node - The node address of the hosting server node 
      @param level - The level of the system that this node is a part of.
      @param isVertex - Indication that this node is the vertex for the
      connectivity graph that would be constructed for the node.
  */ 
  public Gateway(NodeAddress node, short level, boolean isVertex) {
    linkCost = new LinkCostMatrix((short)3);
    this.node = node;
    this.level = level;
    lateralAndUpLink = new Vector();
    downLink    = new Vector();
    this.isVertex = isVertex;
  }

  /** This constructor is used to process the information that would be
      exchanged during the process of unit addition or the addition of a
      gateway.
      @param vertexNode - The node address of the hosting server node 
      @param node - The nodeAddress of the node that is added to the 
      connectivity graph.
      @param level - The level of the system that this node is a part of.
  */ 
  public Gateway(Gateway vertexNode, NodeAddress node, short level) {
    this.vertexNode = vertexNode;
    linkCost = new LinkCostMatrix((short)3);
    this.node = node;
    this.level = level;
    lateralAndUpLink = new Vector();
    downLink    = new Vector();
  }
  

  /** Provides the node address of the gateway node in the connectivity graph.
      @return The node address of the gateway node.
  */
  public NodeAddress getNode() {
    return node;
  }
  
  /** Returns the level that this node exists in within the connectivity graph
      @return The level of this gateway node */
  public short getLevel() {
    return level;
  }
  
  /** This hooks up two gateway nodes in the connectivity graph. The gateway
      on which this method is invoked is responsible for creating the
      edge between the two gateways within the connectivity graph 
      @param gateway - Specifies the gateway node that <i>this</i> node is
      connected to.
  */
  public void connectedToGateway(Gateway gateway) {
    short levelInfo = gateway.getLevel();
    if (levelInfo > level) {
      downLink.addElement(gateway);
      gateway.createLateralAndUpLink(this);
      return;
    }
    if (levelInfo < level) {
      lateralAndUpLink.addElement(gateway);
      gateway.createDownLink(this);
      return;
    }
    lateralAndUpLink.addElement(gateway);
    gateway.createLateralAndUpLink(this);
  }
  
  protected void createLateralAndUpLink(Gateway gateway) {
    lateralAndUpLink.addElement(gateway);
  }
  
  protected void createDownLink(Gateway gateway) {
    downLink.addElement(gateway);
  }
  
  protected void removeLateralAndUpLink(Gateway gateway) {
    lateralAndUpLink.removeElement(gateway);
  }
  
  protected void removeDownLink(Gateway gateway) {
    downLink.removeElement(gateway);
  }
  

  /** When a level-l gateway fails, or is added, the shortest paths and next 
      best hop to reach the vertex node needs to be computed for all level-l 
      gateways, and l+1 ... till the maximum level of the system. This method,
      accounts for the link failure case between two nodes. A unit failure 
      corresponds to the failure of all links within that unit 
      @param gateway - The gateway that has been disconnected.
      @return partitionStatus
  */
  public int disconnectedFromGateway(Gateway gateway) {
    short levelInfo = gateway.getLevel();
    
    if (levelInfo <= level) {
      if (!lateralAndUpLink.contains(gateway)) {
	System.out.println("Gateway: disconnected gateway " + gateway + 
			   " was never connected to " + this );
      }
      lateralAndUpLink.removeElement(gateway);
      gateway.removeLateralAndUpLink(this);
    }
    
    if (levelInfo > level) {
      if (!downLink.contains(gateway)) {
	System.out.println("Gateway: disconnected gateway " + gateway + 
			   " was never connected to " + this );
      }
      downLink.removeElement(gateway);
      gateway.removeDownLink(this);
    }
    return reportPartitionStatus();
  }
  
  /** If the lateralAndUpLink sizes are zero, a node is unit partitioned.
      If however there are no links originating from a node, that node is
      system partitioned 
      @return partitionStatus
  */
  public int reportPartitionStatus() {
    if (lateralAndUpLink.size() == 0) {
      if (downLink.size() == 0) {
	return Protocol.SYSTEM_PARTITION;
      }
      return Protocol.UNIT_PARTITION;
    }
    return 0;
  }
  
  /** This method forces the invalidation of the vertex path so that a 
      new path is forced to be computed */
  public void invalidateVertexPath() {
    vertexPath = null;
  }

  /** The recursive call which computes the cost required to reach the vertex
      gateway within the system. The rules for traversing the connectivity
      graph include traversal only along lateral links or to a lower gateway.
      @param path - The path that is taken by each recursive process. We need
      this to ensure that each path traverses a gateway node only once and 
      does not get stuck inside a recursive infinite loop.
  */
  public Path computeShortestPathToVertex(Path path) {
    //System.out.println("In node (" + node +")");
    
    if (isVertex) return path;
    
    if(vertexPath != null) {
      if (Gateway_Debug) {
	System.out.println("Gateway: Shortest Path from node (" + 
			   node +") being used"); 
      }
      path.updatePath(vertexPath);
      return path;
    }
    Path _path = path.createCopy();
    _path.addCost((short)1000);
    
    Gateway tempBestNode;
    if (lateralAndUpLink.size() != 0) {
      for (Enumeration e =lateralAndUpLink.elements(); e.hasMoreElements();){
	Gateway gateway = ((Gateway)e.nextElement());
	if (!path.nodeTraversed(gateway)) {
	  Path tempPath = path.createCopy();
	  tempPath.addHop(this);
	  tempPath.addCost(linkCost.computeCost(this.getLevel(), 
						gateway.getLevel()));
	  tempPath = gateway.computeShortestPathToVertex(tempPath);
	  if (tempPath.getCost() < _path.getCost()) {
	    _path = tempPath;
	  }		  
	}
      }
    }
    vertexPath = _path;
    return _path;
  }

  /** The recursive call which computes the cost required to reach any given
      gateway within the system. The rules for traversing the connectivity
      graph include traversal only along lateral links or to a lower gateway.
      @param gateway - The gateway that you seek to reach.
      @param path - The path that is taken by each recursive process. We need
      this to ensure that each path traverses a gateway node only once and 
      does not get stuck inside a recursive infinite loop.
  */
  public Path computeShortestPathTo(Gateway gateway, Path path) {
    //System.out.println("In node (" + node +")");
    if (gateway.getNode().equals(node)) return path;
    
    Path _path = path.createCopy();
    _path.addCost((short)1000);

    Gateway tempBestNode;
    if (lateralAndUpLink.size() != 0) {
      for (Enumeration e =lateralAndUpLink.elements(); e.hasMoreElements();){
	Gateway g = ((Gateway)e.nextElement());
	if (!path.nodeTraversed(g)) {
	  Path tempPath = path.createCopy();
	  tempPath.addHop(this);
	  tempPath.addCost(linkCost.computeCost(this.getLevel(), 
						g.getLevel()));
	  tempPath = g.computeShortestPathTo(gateway, tempPath);
	  if (tempPath.getCost() < _path.getCost()) {
	    _path = tempPath;
	  }		  
	}
      }
    }
    return _path;
  }
  
  /** Based on the routing information received at a server node, we need
      to know wether or not we should employ certain hops in the routing
      information. This method provides information regarding wether or not
      the <i>hop</i> (this node)  in question can be employed in routing
      given a certain routing information.
      @param routingInformation - The routing information associated with the
      message so far.
      @return <i>true</i> - If this hop can be employed in the routing, and
      <i>false </i> otherwise.
  */
  public boolean containedInRoutingInformation(byte[] routingInformation) {
    int systemLevel = routingInformation.length/4-1;
    if (systemLevel < level) {
      return false;
    }
    /** The remainder of this method procceds with the assumption that we
	have the Gateway.level routing information available to us within
	the routingInformation. */
    
    /** Step 1: Retrieve the routingInformation for the level of the gateway in
	question */
    byte[] levelInfo = new byte[4];
    System.arraycopy(routingInformation, (systemLevel-level)*4, 
		     levelInfo, 0, 4);
    /** Step 2: Perform a logical & of the level <i>l</i> identifiers in the 
	nodeAdd and the routing information */
    int routeInfoAtLevel = ByteUtilities.getInt(levelInfo);
    int addressAtLevel   = ByteUtilities.getInt(node.getAddressInBytes());
    int decision = routeInfoAtLevel & addressAtLevel;
    /** Step 3: If the result of the &'ing operation is a 0, it implies that 
	the event wasn't routed to the gatewayNode, if it is >= 1 it implies
	that the event was indeed routed here */
    if ( decision !=0 ) {
      return false; /** the event was not routed to this hop ... so this hop
			can be used */
    }
    return true; /** The event was indeed received at this hop */
  }

  /** Based on the routing information received at a server node, we need
      to know wether or not we should employ certain hops in the routing
      information. This method provides information regarding wether or not
      the <i>hop</i> (this node)  in question can be employed in routing
      given a certain routing information.
      @param routingInformation - The routing information associated with the
      message so far.
      @return <i>true</i> - If this hop can be employed in the routing, and
      <i>false </i> otherwise.
  */
  public boolean 
  containedInRoutingInformation(Destinations routingInformation) {
    /** Step 1: Retrieve the routingInformation for the level of the gateway in
	question */
    int routeInfoAtLevel = routingInformation.getDestinationsAtLevel(level);
    int addressAtLevel   = ByteUtilities.getInt(node.getAddressInBytes());
    
    /** Step 2: Perform a logical & of the level <i>l</i> identifiers in the 
	nodeAddress and the routing information */
    int decision = routeInfoAtLevel & addressAtLevel;
    /** Step 3: If the result of the &'ing operation is a 0, it implies that 
	the event wasn't routed to the gatewayNode, if it is >= 1 it implies
	that the event was indeed routed here */
    if ( decision ==0 ) {
      return false; /** the event was not routed to this hop ... so this hop
			can be used */
    }
    return true;
  }


  /** This method is implemented to enable a System.out.println() on the
      gateway node. The string returned provides info regarding the node.
  */
  public String toString() {
    String temp = node.toString() +"(" + level+ ") " ;
    /*
      if (lateralAndUpLink.size() != 0) {
      temp += "Lateral and Up Links";
      for (Enumeration e = lateralAndUpLink.elements();e.hasMoreElements();) {
      temp += ((Gateway)e.nextElement()).getNode() + " ";
      }
      }
      if (downLink.size() != 0) {
      temp += "Down link to node";
      for (Enumeration e = downLink.elements(); e.hasMoreElements(); ) {
      temp += ((Gateway)e.nextElement()).getNode() + " ";
	}
      } */
    return temp;
  }

  /**
     Compares two objects for equality. Returns a boolean that indicates
     whether this object is equivalent to the specified object. This method
     is used when an object is stored in a hashtable.
     @param obj the Object to compare with
     @return true if these Objects are equal; false otherwise.
     @see java.util.Hashtable
  */
  public boolean equals(Object obj) {
    if (node.equals(((Gateway)obj).getNode())) {
      if (level == ((Gateway)obj).getLevel()) {
	return true;
      }
    }
    return false;
  }

  /**
     Generates a hash code for the receiver. This method is supported 
     primarily for hash tables, such as those provided in java.util.
     If two objects are equal (equals(Object) returns true) they must have the 
     same hash code
     @return an integer hash code for the receiver 
  */
  public int hashCode() {
    return node.hashCode()*level;
  }
  
  public static void main(String[] args) {
    System.out.println("Gateway .... ");
    Hashtable hashtable = new Hashtable();
    int[] a6 = {1}; 
    int[] a4 = {2}; 
    int[] a5 = {3}; 
    int[] aa = {7}; 
    int[] ab = {8};
    int[] ac = {9}; 
    int[] aSC_2 = {10}; 
    int[] aSC_3 = {11}; 
    int[] aSSC_B = {20};
    int[] aSSC_C = {21};
    int[] aSSC_D = {22};

    Gateway g6 = new Gateway(new NodeAddress(a6), (short)0, true);
    Gateway g4 = new Gateway(g6, new NodeAddress(a4), (short)0);
    Gateway g5 = new Gateway(g6, new NodeAddress(a5), (short)0);
    Gateway ga = new Gateway(g6, new NodeAddress(aa), (short)1);
    Gateway gb = new Gateway(g6, new NodeAddress(ab), (short)1);
    Gateway gc = new Gateway(g6, new NodeAddress(ac), (short)1);
    Gateway gSC_3 = new Gateway(g6, new NodeAddress(aSC_3), (short)2);
    Gateway gSC_2 = new Gateway(g6, new NodeAddress(aSC_2), (short)2);
    Gateway gSSC_B = new Gateway(g6, new NodeAddress(aSSC_B), (short)3);
    Gateway gSSC_D = new Gateway(g6, new NodeAddress(aSSC_D), (short)3);
    Gateway gSSC_C = new Gateway(g6, new NodeAddress(aSSC_C), (short)3);
    g6.connectedToGateway(g4);
    g6.connectedToGateway(g5);
    g4.connectedToGateway(ga);
    gb.connectedToGateway(ga);
    gb.connectedToGateway(gc);
    gc.connectedToGateway(ga); 
    gb.connectedToGateway(gSC_3);
    gSC_2.connectedToGateway(gSC_3);
    gSC_2.connectedToGateway(g5);
    gSC_2.connectedToGateway(gSSC_C);
    gSSC_B.connectedToGateway(gSC_3);
    gSSC_B.connectedToGateway(ga);
    gSSC_B.connectedToGateway(gSSC_D);
    
    Gateway gateway = gSSC_B;
    // System.out.println("Shortest Path to node(6)from ("+gateway.getNode()+
    //")costs" +  gateway.computeShortestPathTo(g6,path).getCost());
    gateway.computeShortestPathToVertex(new Path());
    Path tester = gateway.computeShortestPathToVertex(new Path());
    Gateway[] test = tester.getHopsTraversed();
    System.out.print("Shortest Path to node (6) from (" + gateway.getNode() 
		     + ") =>> ");
    for (int i=0; i < test.length; i++) {
      System.out.print(test[i]);
    }
    System.out.println("Total Cost =" + tester.getCost());
    gateway = gSSC_B;
    gSSC_C.computeShortestPathToVertex(new Path());
    gateway.computeShortestPathToVertex(new Path());
    ga.disconnectedFromGateway(gb);
    
    int y = gb.disconnectedFromGateway(ga);
    switch (y) {
    case Protocol.UNIT_PARTITION: 
      System.out.println(gb + " is unit partitioned");
      break;
    case Protocol.SYSTEM_PARTITION: 
      System.out.println(gb + " is system partitioned");
      break;
    default:
      System.out.println(gb + " all fine " + y);
      break;
    }
    int x = gb.disconnectedFromGateway(gSC_3);
    switch (x) {
    case Protocol.UNIT_PARTITION: 
      System.out.println(gb + " is unit partitioned");
      break;
    case Protocol.SYSTEM_PARTITION: 
      System.out.println(gb + " is system partitioned");
      break;
    default:
      System.out.println(gb + " all fine " + x);
      break;
    }
    
    if (gb.computeShortestPathToVertex(new Path()).getCost()> 1000) {
      System.out.println(gb + " is system partitioned");
    }
    
   }
}
